package com.medium.house.utils;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.security.Key;
import java.security.spec.AlgorithmParameterSpec;

/**
 * Secure & Digest methods
 * <p>
 * Created by Milton on 2015/8/21 at 19:03.
 */
public class SecureUtil {
    public static final RecyclableAtomicInteger atomic = new RecyclableAtomicInteger();
    public static final int THRESHOLD = 65536;

    private static Logger logger = LoggerFactory.getLogger(SecureUtil.class);

    public static String uuid() {
        return java.util.UUID.randomUUID().toString().replace("-", "");
    }

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param byteArray
     * @return
     */
    public static String byteToStr(byte[] byteArray) {
        String strDigest = "";
        for (int i = 0; i < byteArray.length; i++) {
            strDigest += byteToHexStr(byteArray[i]);
        }
        return strDigest;
    }

    /**
     * 将字节转换为十六进制字符串
     *
     * @param mByte
     * @return
     */
    public static String byteToHexStr(byte mByte) {
        char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        char[] tempArr = new char[2];
        tempArr[0] = digits[(mByte >>> 4) & 0X0F];
        tempArr[1] = digits[mByte & 0X0F];

        String s = new String(tempArr);
        return s;
    }

    /**
     * Get the 32-character MD5 result
     *
     * @param s: input string
     * @return
     */
    public static String md5(String s) {
        return DigestUtils.md5Hex(s);
    }

    /**
     * Get MD5 value of the input string
     *
     * @param s:      input string
     * @param length: output length
     * @return MD5 result
     */
    public static String md5(String s, int length) {
        String output = null;
        length = (length > 32 || length < 0) ? 32 : length;
        output = DigestUtils.md5Hex(s);
        return output.substring(0, length);
    }

    /**
     * Generate the password hash with random salt before store it
     *
     * @param password plain password
     * @return hashed password
     */
    public static String hash(String password) {
        String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
        double d = Math.random();
        int ran_index = (int) Math.floor(d * 64) % 64;
        String ran_string = String.valueOf(d);
        char ran_char = itoa64.charAt(ran_index);
        String setting = "$H$";
        setting += ran_char;
        setting += DigestUtils.md5Hex(ran_string).substring(0, 8);
        return hash_crypt_private(password, setting);
    }

    /**
     * Generate the hash with the salt contained in the second parameter.
     * The result string should be the same as the second parameter if the password is correct
     *
     * @param password: password input
     * @param setting:  hashed password in native storage
     * @return hashed password
     */
    public static String hash_crypt_private(String password, String setting) {
        String output = "*";
        if (!setting.substring(0, 3).equals("$H$")) {
            return output;
        }
        char c = setting.charAt(3);
        c = (char) ((c % 7) + 3);
        int count = 1 << c;
        String salt = setting.substring(4, 12);
        if (salt.length() != 8) {
            return output;
        }
        for (int i = 0; i < count; i++) {
            salt = salt + password;
            salt = DigestUtils.md5Hex(salt);
        }
        output = setting.substring(0, 12);
        output += salt.substring(0, 22);

        return output;
    }

    /**
     * DES编码
     *
     * @param key  长度不能小于8字节
     * @param data
     * @return
     */
    public static String desEncode(String key, byte[] data) {
        try {
            DESKeySpec dks = new DESKeySpec(key.getBytes());
            Key secretKey = SecretKeyFactory.getInstance("DES").generateSecret(dks);
            Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
            AlgorithmParameterSpec paramSpec = new IvParameterSpec("12345678".getBytes());// 向量
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec);
            byte[] bytes = cipher.doFinal(data);
            return new Base64().encodeToString(bytes);
        } catch (Exception e) {
            logger.error(e.getMessage());
            logger.debug(e.getMessage(), e);
        }
        return null;
    }

    /**
     * DES解码
     *
     * @param key  长度不能小于8字节
     * @param data
     * @return
     */
    public static byte[] desDecode(String key, byte[] data) {
        try {
            DESKeySpec dks = new DESKeySpec(key.getBytes());
            Key secretKey = SecretKeyFactory.getInstance("DES").generateSecret(dks);
            Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
            AlgorithmParameterSpec paramSpec = new IvParameterSpec("12345678".getBytes());
            cipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec);
            return cipher.doFinal(data);
        } catch (Exception e) {
            logger.error(e.getMessage());
            logger.debug(e.getMessage(), e);
        }
        return null;
    }

    public static final char[] captchars = {
            '3', '3', '4', '6', '6', '7',
            '8', '9', 'a', 'b', 'c', 'd',
            'e', 'f', 'g', 'h', 'j', 'k',
            'l', 'm', 'n', 'p', 'q', 'r',
            'f', 't', 'u', 'v', 'w', 'x',
            'y', 'q'
    };

    /**
     * 去掉0, 1, i, o等易混淆的字母, 生成校验码字符串
     *
     * @param i
     * @param length
     * @return
     */
    public static String toCaptchaString(long i, int length) {
        char[] buf = new char[64];
        int charPos = 64;
        int radix = 1 << 5;
        long mask = radix - 1;
        do {
            buf[--charPos] = captchars[(int) (i & mask)];
            i >>>= 5;
        } while (i != 0);

        if (length == 0)
            return new String(buf, charPos, (64 - charPos));
        else {
            while (charPos > 64 - length) {
                buf[--charPos] = 'c';
            }
            return new String(buf, 64 - length, length);
        }
    }

    public static final char[] digits64 = gen64char();

    /**
     * Generate a 64-char array, started with '-','.',followed with 0~9, A~Z, a~z
     *
     * @return
     */
    private static char[] gen64char() {
        char[] chars = new char[64];
        char start = '0';
        int pos = 0;
        chars[pos++] = '-';
        chars[pos++] = '.';
        for (int i = 0; i < 10; i++) chars[pos++] = (char) (start + i);
        start = 'A';
        for (int i = 0; i < 26; i++) chars[pos++] = (char) (start + i);
        start = 'a';
        for (int i = 0; i < 26; i++) chars[pos++] = (char) (start + i);
        return chars;
    }

    public static final char[] digits32 = gen32char();

    /**
     * Generate a 32-char array, started with 0~9, followed with A~Z, a~z
     *
     * @return
     */
    private static char[] gen32char() {
        char[] chars = new char[32];
        char start = '0';
        int pos = 0;
        for (int i = 0; i < 10; i++) chars[pos++] = (char) (start + i);
        start = 'A';
        for (int i = 0; i < 26 && pos < 32; i++) chars[pos++] = (char) (start + i);
        return chars;
    }

    /**
     * Convert the integer to an unsigned number.
     * toUnsignedString(i, 4) equals to toHexString(i);
     *
     * @param i     positive integer
     * @param shift 1~6
     * @return
     */
    public static String toUnsignedString(long i, int shift) {
        if (i < 0) i = 0 - i;
        if (shift > 6) shift = 6;
        if (shift < 1) shift = 1;

        char[] buf = new char[64];
        char[] digits = (shift > 5) ? digits64 : digits32;
        int charPos = 64;
        int radix = 1 << shift;
        long mask = radix - 1;
        do {
            buf[--charPos] = digits[(int) (i & mask)];
            i >>>= shift;
        } while (i != 0 && charPos >= 0);
        return new String(buf, charPos, (64 - charPos));
    }

    /**
     * Generate a sequence number follow the time
     * only 10e-5 possibility of revert order in a second,
     * which can be easily detected in real work
     *
     * @param compress in decimal numbers(false) or duotricemary numbers(true)
     * @return String
     */
    public static String sequenceId(boolean compress) {
        long now = System.currentTimeMillis();
        int seq = atomic.incrementAndRecycle(THRESHOLD);
        now = now * 100000 + seq;
        if (compress)
            return toUnsignedString(now, 5);
        else
            return String.valueOf(now);
    }

    /**
     * Generate the unique invite code for each user, basing on timestamp
     *
     * @return String
     */
    public static String inviteCode() {
        long now = System.currentTimeMillis() - 1419120000000L;
        long ran = Math.round(Math.random() * 100);
        now = now * 100 + ran;
        return toUnsignedString(now, 5);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100000; i++) {
            /*long now = System.currentTimeMillis() - 946080000000L;
            long ran = Math.round(Math.random() * 100);
            now = now * 100 + ran;
            System.out.println(now);*/
            System.out.println(sequenceId(false));
            System.out.println(inviteCode());
            //System.out.println(toUnsignedString(now, 6));
        }
    }
}
